package ServerPackage;

import ClientPackage.ClientInfo;
import SecureUtil.CookieHelper;
import SecureUtil.DiffieHellman;
import SecureUtil.PBEHelper;
import SecureUtil.Packer;
import SecureUtil.RSAHelper;
import SecureUtil.UDPHelper;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;

/**
 * This is the center server to authenticate Clients and update the on-line user list
 * It would wait for any request then spawn a new thread to process each request.
 * It has the record for username and password.
 * 
 * @author Zhisheng
 *
 */
public class CenterServer {
    static CenterServer _instance = null;
    // Server
    public InetAddress _serverIp;
    public int _serverPort;
    public PublicKey _serverpubKey;
    public PrivateKey _serverprivKey;
    public DatagramSocket _serverSocket;
    public int cookieseed = new Random().nextInt();
    public CookieHelper cookiemanager;
    public ArrayList<Credentials> _teamCredentials;
    public ArrayList<ClientInfo> _onlineUserList;
    public int IPCounter;
    public InetAddress lastIp;
    public long lastLoginReqtime;
    private final String LOGIN = "LOGIN";
    private final String LOGOUT = "LOGOUT";
    private final String UPDATE_LIST = "UPDATE_LIST";
    
    private CenterServer() throws Exception{
    	RSAHelper rsaManager = new RSAHelper();
    	_serverpubKey = rsaManager.getPublicKeyFromFile("Server_public_key");
    	_serverprivKey = rsaManager.getPrivateKeyFromFile("Server_private_key");
    	_onlineUserList = new ArrayList<ClientInfo> ();
        this.initTeamCreadentials();
    }
    
    public static CenterServer getInstance() throws Exception{
        if(_instance == null) {
            _instance = new CenterServer();
        }
        return _instance;
    }
    public static void main(String[] args) {
    	CenterServer server;
    	try{
    		server = CenterServer.getInstance();
            server._serverIp = InetAddress.getLocalHost();

            //server._serverPort = Integer.parseInt(args[0]); //input the port number to pass the parameter to java
            server._serverPort  = 1234;
            

            //Create the server socket on the specified port
            server._serverSocket = new DatagramSocket(server._serverPort, server._serverIp);

            System.out.println("---------------- IP: " + server._serverIp.getHostAddress() + "---------------");
            System.out.println("---------- IM Server (Port " + server._serverPort + ")-----------");
            System.out.println();
            System.out.println("Server Initialized...START Listening to any request......\n");
            //Till process is not killed keep listening for connections
            //server.cookiemanager = new CookieHelper();
    	}catch(Exception e){
    		e.printStackTrace();
    		System.err.println("failed to initialize server");
    		return;
    	}
            while (true) {
            	try {
                UDPHelper udpDelegate = new UDPHelper(server._serverSocket); //this socket listens for incoming message
                byte[] recvBytes = udpDelegate.receiveBytes(0);
                Packer packer = new Packer();
                packer.unpack(recvBytes);
                String cookieback;
                String cmd;
                
                try {
                    cmd = (String) packer.getNext(Packer.DataType.STRING);
                    cookieback = (String) packer.getNext(Packer.DataType.STRING);
                } catch (Exception e) {
                    System.out.println("illegal query when parseing in server");
                    continue;
                }

                String cookie = CookieHelper.getCookie(udpDelegate.fromip,udpDelegate.fromport, server.cookieseed);

                // if there is no cookie;
                if (cookieback.equalsIgnoreCase("NULL")) {
                    udpDelegate.sendBytes(cookie.getBytes(),udpDelegate.fromip, udpDelegate.fromport);
                    continue;
                } else if (!cookieback.equals(cookie)) {
                    // if the cookie is not equal;
                    System.out.println("cookie does not match: " +
                            cookie + " (cal) != " + cookieback + " (back)");
                    continue;
                }
                //cookie matches;
                if (cmd.equalsIgnoreCase(server.LOGIN)) {
                	// if same iP log in time login consecutively in 20 sec, ignore the request;   
                	// we allow dos attacking from two IPs alternatively  with correct cookie, enjoy your attacking :)
                	System.out.println("Received a safe Login request.");
                    if(server.lastIp==null 
                    		|| server.lastLoginReqtime+60000<new Date().getTime()
                    		|| !server.lastIp.equals(udpDelegate.fromip)){ 
                    	server.IPCounter=0;
                    	server.lastIp = udpDelegate.fromip;
                    	server.lastLoginReqtime = new Date().getTime();
                    }else if(server.IPCounter>3){
                    	System.out.println("warning: experiencing Dos Attacking --> login request ignored");
                    	continue;
                    }else{
                    	server.IPCounter++;
                    }
                    
                    byte[] initmsg = null;
                    try {
                        initmsg = (byte[]) packer.getNext(Packer.DataType.BYTES);
                    } catch (Exception e) {
                        System.err.println("warning: login user with cookie " +
                                "is not carrying message -- ignore login request");
                        continue;
                    }
                    System.out.println("Open a new thread to assist Login.");
                    new Thread(new LoginAssistant(server,udpDelegate.fromip,
                            udpDelegate.fromport, initmsg)).start();
                    continue;
                } else {
                    // verify the user's command
                    ClientInfo c = server.searchOnlineByAddress(
                            udpDelegate.fromip);
                    if(c==null) {
                        System.err.println("list request from unloged in address -> ignored");
                        continue;
                    }
                    System.err.println("user has logged on, verifying the request");
                    //byte[] verifymsg = (byte[]) packer.getNext(Packer.DataType.BYTES);
                    //DiffieHellman dh = new DiffieHellman();
                    //byte[] cmd_decoded = dh.decryptDH(verifymsg, c._sessionkey);
                    //if(Arrays.equals(cmd_decoded, cmd.getBytes())) {
                        // the user's command get verified;
                        if(cmd.equals(server.UPDATE_LIST)){
                        	System.out.println("get update_list request");
                            server.sendListTo(c, udpDelegate.fromport);
                        }
                        if(cmd.equals(server.LOGOUT)){
                        	System.out.println("get logout request");
                            server.logout(c, udpDelegate.fromport);
                        }
                    //}else{
                    //	System.err.println("not matched");
                    //	System.err.println(cmd+" : "+new String(cmd_decoded));
                    //}
            }
        } catch (Exception e) {
            e.printStackTrace();
            continue;
        }
        }
    }
    public void logout(ClientInfo c, int toport){
        this._onlineUserList.remove(c);
        UDPHelper sender = new UDPHelper(this._serverSocket);
        try{
            sender.sendBytes(LOGOUT.getBytes(), c._ip, toport, c._sessionkey);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public void sendListTo(ClientInfo to, int toport) throws Exception{
        Packer packer = new Packer();
        packer.addElement(Packer.DataType.INTEGER, _onlineUserList.size());
        System.out.println("online user num: "+_onlineUserList.size());
        for(ClientInfo c : this._onlineUserList){
            byte[] clientBytes = c.toByteArray();
            packer.addElement(Packer.DataType.BYTES, clientBytes);
        }
        DiffieHellman dh = new DiffieHellman();
        byte[] encrypted = dh.encryptDH(packer.pack(), to._sessionkey);
        UDPHelper sender = new UDPHelper(this._serverSocket);
        sender.sendBytes(encrypted, to._ip, toport, to._sessionkey);
    }
    public void loginUser(ClientInfo c){
    	ClientInfo client = searchOnlineByAddress(c._ip);
        if(client!=null) this._onlineUserList.remove(client);
        
        client = searchOnlineUserByName(c._username);
        if(client!=null) this._onlineUserList.remove(client);
        
        this._onlineUserList.add(c);
    }
    public ClientInfo searchOnlineByAddress(InetAddress ip){
        try{
            for(ClientInfo c : _onlineUserList){
                if(c._ip.equals(ip)){
                    return c;
                }
            }
            return null;
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }
    public ClientInfo searchOnlineUserByName(String userName){
        Iterator<ClientInfo> iter = _onlineUserList.iterator();
        while(iter.hasNext()){
            ClientInfo c = iter.next();
            if(c._username.equals(userName)) return c;
        }
        return null;
    }
    public Credentials searchRegistedUserByName(String userName){
        Iterator<Credentials> iter = _teamCredentials.iterator();
        while(iter.hasNext()){
            Credentials c = iter.next();
            if(c._userName.equals(userName)) return c;
        }
        return null;
    }
    
    private void initTeamCreadentials() throws Exception {

        _teamCredentials = new ArrayList<Credentials>();

        _teamCredentials.add(new Credentials("t1user1", PBEHelper.getPasswordHash("jusi28381t"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t1user2", PBEHelper.getPasswordHash("asdf345457"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t2user1", PBEHelper.getPasswordHash("sdf9823q4asdf"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t2user2", PBEHelper.getPasswordHash("asfd823h78sdaf"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t3user1", PBEHelper.getPasswordHash("1234g9ofguie"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t3user2", PBEHelper.getPasswordHash("sdfwerki9jk37"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t4user1", PBEHelper.getPasswordHash("234jlk8asdf23"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t4user2", PBEHelper.getPasswordHash("po923fuis3"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t5user1", PBEHelper.getPasswordHash("234g534sff"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t5user2", PBEHelper.getPasswordHash("6fsd3gh663"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t6user1", PBEHelper.getPasswordHash("3247jg98fit43"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t6user2", PBEHelper.getPasswordHash("034iugrgi4qwt"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t7user1", PBEHelper.getPasswordHash("j4t4qsferadsf"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t7user2", PBEHelper.getPasswordHash("l92345sgaje"), PBEHelper.generateSalt()));
        

        _teamCredentials.add(new Credentials("t8user1", PBEHelper.getPasswordHash("0po2398ff2f"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t8user2", PBEHelper.getPasswordHash("90438asdlf"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t9user1", PBEHelper.getPasswordHash("f234fpfdf"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t9user2", PBEHelper.getPasswordHash("sadf234ghnadf"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("t10user1", PBEHelper.getPasswordHash("23d5sdad7d"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t10user2", PBEHelper.getPasswordHash("asd5g6gju78"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t11user1", PBEHelper.getPasswordHash("a3hl9j6r32"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t11user2", PBEHelper.getPasswordHash("n8u436eas5hdf54"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t12user1", PBEHelper.getPasswordHash("fsfd3gas21d"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t12user2", PBEHelper.getPasswordHash("asdf6j84fgb"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t13user1", PBEHelper.getPasswordHash("daf3er67y5y4we"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t13user2", PBEHelper.getPasswordHash("43f6h8b12d4f"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t14user1", PBEHelper.getPasswordHash("2f5fds77r53fg"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t14user2", PBEHelper.getPasswordHash("0jfds7n6d23"), PBEHelper.generateSalt()));
       
        _teamCredentials.add(new Credentials("t15user1", PBEHelper.getPasswordHash("3f32g67rdg78f"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t15user2", PBEHelper.getPasswordHash("b7t49fi83ds"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t16user1", PBEHelper.getPasswordHash("p39ldfi893j"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t16user2", PBEHelper.getPasswordHash("90jkldf3hd2"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t17user1", PBEHelper.getPasswordHash("0odf83ie743"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t17user2", PBEHelper.getPasswordHash("k9k783jhd74gg6"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t18user1", PBEHelper.getPasswordHash("sdfr32g5fhsg"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t18user2", PBEHelper.getPasswordHash("gf65nbtr78nbs"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t19user1", PBEHelper.getPasswordHash("9k37ju3a2q"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t19user2", PBEHelper.getPasswordHash("mvcnv6bxcvz"), PBEHelper.generateSalt()));
        
        _teamCredentials.add(new Credentials("t20user1", PBEHelper.getPasswordHash("bujnv832dhf783"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t20user2", PBEHelper.getPasswordHash("f7jvnbvc6gh32"), PBEHelper.generateSalt()));
       
        _teamCredentials.add(new Credentials("t21user1", PBEHelper.getPasswordHash("239i23g21dff"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("t21user2", PBEHelper.getPasswordHash("0ojbj73jnf2"), PBEHelper.generateSalt()));
          
        _teamCredentials.add(new Credentials("TA1", PBEHelper.getPasswordHash("gh567mkgg3"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("TA2", PBEHelper.getPasswordHash("yu5676dfk4"), PBEHelper.generateSalt()));

        _teamCredentials.add(new Credentials("Instructor1", PBEHelper.getPasswordHash("23sdffasf3"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("Instructor2", PBEHelper.getPasswordHash("lpisdfw32a"), PBEHelper.generateSalt()));

        //Testing account
        _teamCredentials.add(new Credentials("a", PBEHelper.getPasswordHash("a"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("b", PBEHelper.getPasswordHash("b"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("c", PBEHelper.getPasswordHash("c"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("d", PBEHelper.getPasswordHash("d"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("e", PBEHelper.getPasswordHash("e"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("f", PBEHelper.getPasswordHash("f"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("g", PBEHelper.getPasswordHash("g"), PBEHelper.generateSalt()));
        _teamCredentials.add(new Credentials("h", PBEHelper.getPasswordHash("h"), PBEHelper.generateSalt()));
    }
}
	
		
